﻿module Core
    type Expr = | Plus of Expr * Expr
                | Times of Expr * Expr
                | Var of int
                | Zero

    type Cmd = | Skip
               | Asgn of int * Expr
               | Choice of Cmd * Cmd
               | Seq  of Cmd * Cmd
               | Loop of int * Cmd

    let rec NumOfVarsInExpression = function
        | Var(i) -> i
        | Zero -> 1
        | Plus(e1,e2) -> max (NumOfVarsInExpression e1) (NumOfVarsInExpression e2)
        | Times(e1, e2)-> max (NumOfVarsInExpression e1) (NumOfVarsInExpression e2)

    and NumOfVars = function
        | Skip          -> 1
        | Asgn(j,e)     -> max j (NumOfVarsInExpression e)
        | Seq(C1,C2)    -> max (NumOfVars C1) (NumOfVars C2)
        | Choice(C1,C2) -> max (NumOfVars C1) (NumOfVars C2)
        | Loop(l,C)     -> max l (NumOfVars C)

    let rec prtExpr = function
        | Zero            -> "0"
        | Var n           -> ("X" + n.ToString())
        | (Plus(e1, e2))  -> prtExpr(e1) + " + " + prtExpr(e2)
        | (Times(e1, e2)) -> prtInnerExpr(e1) + " * " + prtInnerExpr(e2)
    and prtInnerExpr = function
        | Zero            -> "0"
        | Var n           -> ("X" + n.ToString())
        | (Plus(e1, e2))  -> "( " + prtInnerExpr(e1) + " + " + prtInnerExpr(e2) + " )"
        | (Times(e1, e2)) -> "( " + prtInnerExpr(e1) + " * " + prtInnerExpr(e2) + " )"

    (* print a command. In this version,
      some attention to pretty-printing is done, but no line breaking *)
    let rec pprt = function
        |  Skip         -> "Skip"
        | Asgn(id,e)    -> (prtExpr (Var id)) + " := " + (prtExpr e)
        | Choice(s1,s2) -> "if ? then " + (pprt s1) + " else " + (pprt s2)
        | Seq(s1,s2)    -> "{ " + (pprtNoBrace s1) + "; " + (pprtNoBrace s2) + " }"
        | Loop(id,s)    -> "loop " + (prtExpr (Var id)) + " { " + (pprtNoBrace s) + " }"
    and pprtNoBrace = function
        | Skip as c     -> pprt(c)
        | Asgn(id,e)    -> (prtExpr (Var id)) + " := " + (prtExpr e)
        | Choice(s1,s2) -> "if ? " + " then " + (pprt s1) + " else " + (pprt s2)
        | Seq(s1,s2)    -> (pprtNoBrace s1) + "; " + (pprtNoBrace s2)
        | Loop(id,s)    -> "loop " + (prtExpr (Var id)) + " { " + (pprtNoBrace s) + " }"

    (* the tagged functions do line-breaking. Receive parameters for indentation and
       line-termination *)
    let rec pprti (indent:string) (finish:string) (c:Cmd) =
        match c with
        | Skip          -> indent + pprt(c) + finish
        | Asgn(id,e)    -> indent + (prtExpr (Var id)) + " := " + (prtExpr e) + finish
        | Choice(s1,s2) -> indent + "if ? then\n" + (pprti (indent + "  ") "\n" s1) + indent + "else\n" + (pprti (indent + "  ") finish s2)
        | Seq(s1,s2)    -> indent + "{\n" + (pprtNoBracei (indent + "  ") ";\n" s1) + (pprtNoBracei (indent + "  ") "\n" s2) + indent + "}" + finish
        | Loop(id,s)    -> indent + "loop " + (prtExpr (Var id)) + " {\n" + (pprtNoBracei (indent + "  ") "\n" s) + indent + "}" + finish
    and pprtNoBracei (indent:string) (finish:string) (c:Cmd) =
        match c with
        | Skip          -> indent + pprt(c) + finish
        | Asgn(id,e)    -> indent + (prtExpr (Var id)) + " := " + (prtExpr e) + finish
        | Choice(s1,s2) -> indent + "if ? then\n" + (pprti (indent + "  ") "\n" s1) + indent + "else\n" + (pprti (indent + "  ") finish s2)
        | Seq(s1,s2)    -> (pprtNoBracei indent ";\n" s1) + (pprtNoBracei indent finish s2)
        | Loop(id,s)    -> indent + "loop " + (prtExpr (Var id)) + " {\n" + (pprtNoBracei (indent + "  ") "\n" s) + indent + "}" + finish

     (* finally, the pretty-printer chooses whether to do line-breaking or not *)
    let prt(c:Cmd) : string =
        if (String.length (pprt c)) < 60 then
            (pprt c)+ "\n\n"
        else
            (pprti "" "\n" c)+ "\n"